
/**
 * 构造器是一个特殊的方法，这个特殊方法用于创建实例时执行初始化。
 * 构造器是创建对象的重要途径（即使使用工厂模式、反射等方式创建对象，其实质依然是依赖于构造器），
 * 因此，Java类必须包含一个或一个以上的构造器。
 * <p>
 * 注意：如果程序员没有为Java类提供任何构造器，则系统会为这个类提供一个无参数的构造器，这个构造器的执行体为空，不做任何事情。
 * 无论如何，Java类至少包含一个构造器。
 */
class ConstructorTest {
    public String name;
    public int count;

    // 提供自定义的构造器，该构造器包含两个参数
    public ConstructorTest(String name, int count) {
        // 构造器里的this代表它进行初始化的对象
        // 将传入的2个参数赋给实例变量
        this.name = name;
        this.count = count;
    }

    public static void main() {
        // 使用自定义的构造器来创建对象
        // 系统将会对该对象执行自定义的初始化
        ConstructorTest tc = new ConstructorTest("学习Java", 20200512);
        System.out.println(tc.name);
        System.out.println(tc.count);
        // 运行上面程序，将看到输出ConstructorTest对象时，它的name实例变量不再是null，而且count实例变量也不再是0，这就是提供自定义构造器的作用。
        /*
         * 构造器是创建Java对象的途径，是不是说构造器完全负责创建Java对象？
         *
         * 不是！构造器是创建Java对象的重要途径，通过new关键字调用构造器时，构造器也确实返回了该类的对象，但这个对象并不是完全由构造器负责创建的。
         * 实际上，当程序员调用构造器时，系统会先为该对象分配内存空间，并为这个对象执行默认初始化，这个对象已经产生了——这些操作在构造器执行之前就都完成了。
         * 也就是说，当系统开始执行构造器的执行体之前，系统已经创建了一个对象，只是这个对象还不能被外部程序访问，只能在该构造器中通过this来引用。
         * 当构造器的执行体执行结束后，这个对象作为构造器的返回值被返回，通常还会赋给另一个引用类型的变量，从而让外部程序可以访问该对象。
         */
        /*
         * 一旦程序员提供了自定义的构造器，系统就不再提供默认的构造器，
         * 因此上面的ConstructorTest类不能再通过new Constructor()；代码来创建实例，因为该类不再包含无参数的构造器。
         *
         * 如果用户希望该类保留无参数的构造器，或者希望有多个初始化过程，则可以为该类提供多个构造器。
         * 如果一个类里提供了多个构造器，就形成了构造器的重载。
         *
         * 因为构造器主要用于被其他方法调用，用以返回该类的实例，因而通常把构造器设置成public访问权限，从而允许系统中任何位置的类来创建该类的对象。
         * 除非在一些极端的情况下，业务需要限制创建该类的对象，可以把构造器设置成其他访问权限，
         * 例如设置为protected，主要用于被其子类调用；
         * 把其设置为private，阻止其他类创建该类的实例。
         */
    }
}

/**
 * 同一个类里具有多个构造器，多个构造器的形参列表不同，即被称为构造器重载。
 * 构造器重载和方法重载基本相似：要求构造器的名字相同，这一点无须特别要求，因为构造器必须与类名相同，所以同一个类的所有构造器名肯定相同。
 * 为了让系统能区分不同的构造器，多个构造器的参数列表必须不同。
 */
class ConstructorOverload {
    public String name;
    public int count;

    // 提供无参的构造器
    public ConstructorOverload() {
    }

    // 提供带两个参数的构造器
    // 对该构造器返回的对象进行初始化
    public ConstructorOverload(String name, int count) {
        this.name = name;
        this.count = count;
    }

    public static void main() {
        // 通过五参数构造器创建ConstructorOverload对象
        ConstructorOverload oc1 = new ConstructorOverload();
        // 通过有参数构造器创建ConstructorOverload对象
        ConstructorOverload oc2 = new ConstructorOverload("学习Java", 20200512);
        System.out.println(oc1.name + " " + oc1.count);
        System.out.println(oc2.name + " " + oc2.count);
    }
}

/**
 * 如果系统中包含了多个构造器，其中一个构造器的执行体里完全包含另一个构造器的执行体。
 * 对于这种完全包含的情况，如果是两个方法之间存在这种关系，则可在方法B中调用方法A。
 * 但构造器不能直接被调用，构造器必须使用new关键字来调用。但一旦使用new关键字来调用构造器，将会导致系统重新创建一个对象。
 * 为了在构造器B中调用构造器A中的初始化代码，又不会重新创建一个Java对象，可以使用this关键字来调用相应的构造器。
 * 下面代码实现了在一个构造器中直接使用另一个构造器的初始化代码。
 */
class Apple {
    public String name;
    public String color;
    public double weight;

    public Apple() {
    }

    // 两个参数的构造器
    public Apple(String name, String color) {
        this.name = name;
        this.color = color;
    }

    // 三个参数的构造器
    public Apple(String name, String color, double weight) {
        // 通过this调用另一个重载的构造器的初始化代码
        this(name, color);
        // 下面this引用该构造器正在初始化的Java对象
        this.weight = weight;
        /*
         * 使用this调用另一个重载的构造器只能在构造器中使用，而且必须作为构造器执行体的第一条语句。
         * 使用this调用重载的构造器时，系统会根据this后括号里的实参来调用形参列表与之对应的构造器。
         */
    }
}

public class Constructor {
    public static void main(String[] args) {
        ConstructorTest.main();
        ConstructorOverload.main();
    }
}
